package com.zhou.org.service.impl;

import com.zhou.framework.config.PasswordPolicyConfig;
import com.zhou.framework.constant.CacheKeyEnum;
import com.zhou.framework.domain.DomainBean;
import com.zhou.framework.domain.DomainConfig;
import com.zhou.framework.domain.DomainHelper;
import com.zhou.framework.domain.DomainInfo;
import com.zhou.framework.exception.CaptchaException;
import com.zhou.framework.exception.LoginException;
import com.zhou.framework.exception.UserLockException;
import com.zhou.framework.exception.VerifyException;
import com.zhou.framework.redis.util.RedisUtil;
import com.zhou.framework.util.EnumClassUtil;
import com.zhou.framework.util.SessionUtil;
import com.zhou.framework.util.SettingUtil;
import com.zhou.org.common.LoginModeEnum;
import com.zhou.org.common.PasswordUtil;
import com.zhou.org.dao.User;
import com.zhou.org.dto.LoginRequest;
import com.zhou.org.model.CheckResult;
import com.zhou.org.model.LoginResult;
import com.zhou.org.service.LoginCheck;
import com.zhou.org.service.LoginService;
import com.zhou.org.service.OrgService;
import com.zhou.org.service.UserService;
import com.zhou.util.Assert;
import com.zhou.util.Md5Util;
import com.zhou.util.NumberUtil;
import com.zhou.util.StringTool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.naming.NamingException;
import java.util.Map;
import java.util.Objects;

/**
 * @author lang.zhou
 * @since 2023/2/4
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private UserService userService;
    @Autowired
    private OrgService orgService;

    @Autowired
    private RedisUtil redisUtil;

    @Override
    public LoginResult login(LoginRequest r) {
        String token = SessionUtil.getCurrentToken();

        User user = this.validateUserStatus(r.getUsername());
        if(!SessionUtil.isLogin()){
            this.validateCaptcha(r);
            //String pubKey = SettingUtil.getStringValue("SYSTEM", "pub_key","");
            //解密后的真实密码
            String pwd = PasswordUtil.decrypt(r.getPassword());
            this.userLogin(user,r.getUsername(),pwd);

            token = SessionUtil.login(user);
        }
        return new LoginResult(token,user);
    }

    /**
     * 登录方法
     */
    private void userLogin(User user,String username,String password){
        PasswordPolicyConfig config = PasswordPolicyConfig.getInstance();
        LoginModeEnum mode = EnumClassUtil.get(LoginModeEnum.class, config.getLoginMode());
        if("admin".equals(username)){
            this.loginLocal(user,password);
        }else if(mode == LoginModeEnum.DOMAIN){
            this.loginDomain(user,username,password);
        }else if(mode == LoginModeEnum.THIRD){
            this.loginThird(user,username,password);
        }else if(mode == LoginModeEnum.MIX && user != null){
            this.loginMix(user,username,password);
        }else{
            this.loginLocal(user,password);
        }
        if(user == null){
            log.warn("用户【{}】不存在",username);
            throw new LoginException("用户名或密码错误");
        }
    }

    /**
     * 混合登录
     */
    private void loginMix(User user,String username,String password){
        LoginModeEnum mode = EnumClassUtil.get(LoginModeEnum.class, user.getLoginMode());
        if(mode == LoginModeEnum.DOMAIN){
            this.loginDomain(user,username,password);
        }else if(mode == LoginModeEnum.THIRD){
            this.loginThird(user,username,password);
        }else{
            this.loginLocal(user,password);
        }
    }
    /**
     * 本地登录
     */
    private void loginLocal(User user,String password){
        if(user == null){
            throw new LoginException("用户名或密码错误");
        }
        String hex = Md5Util.hex(password, user.getSalt());
        if(!Objects.equals(hex,user.getPassword())){
            throw new LoginException("用户名或密码错误");
        }
    }

    /**
     * 域登录
     */
    private void loginDomain(User u, String userName,String pwd){
        Map<String, Object> domainMap = SettingUtil.getSettingMap("domain");
        String domainName = MapUtils.getString(domainMap, "domainName");
        String host = MapUtils.getString(domainMap, "host");
        Assert.notEmpty(domainName,"域服务器错误");
        Assert.notEmpty(host,"域服务器错误");
        DomainInfo info = new DomainInfo(domainName, host);
        DomainHelper helper = new DomainHelper();

        DomainConfig domainConfig = new DomainConfig(info);
        domainConfig.setUsername(userName);

        domainConfig.setPassword(pwd);

        DomainBean bean ;

        try {
            helper.connect(domainConfig);
            bean = helper.searchUser(userName);
        }catch (javax.naming.AuthenticationException e){
            throw new LoginException("域用户名或密码错误");
        }catch (NamingException e){
            throw new VerifyException("连接域服务器失败");
        }catch (Exception e){
            throw new VerifyException("域登录失败");
        }finally {
            helper.close();
        }
        if(bean == null){
            throw new LoginException("域用户名或密码错误");
        }
        if(u == null){
            u = new User();
            u.setUsername(userName);
            u.setRealName(StringTool.safeToString(bean.getDisplayName(),userName));
            u.setEmail(StringTool.safeToString(bean.getEmail(),""));
            u.setPhone(StringTool.safeToString(bean.getTel(),""));

            u.setLoginMode(LoginModeEnum.DOMAIN.getEnumValue());
            this.createUser(u,pwd);
        }
    }

    /**
     * 三方对接登录
     */
    private void loginThird(User user,String username,String password){
        String err = null;
        LoginCheck check;
        CheckResult result;
        try{
            Class<?> c = Class.forName(PasswordPolicyConfig.getInstance().getLoginClassName());
            check = (LoginCheck) c.newInstance();
            result = check.login(username,password);
            if(NumberUtil.safeToInteger(result.getStatus(),1) != 0){
                err = StringTool.safeToString(result.getError(),"登录验证失败");
            }

        }catch (Exception e){
            throw new VerifyException("登录失败");
        }
        if(StringTool.isNotEmpty(err)){
            throw new LoginException(err);
        }
        if(user == null){
            user = check.newUser(result);
            if(user != null){
                user.setUsername(username);
                user.setLoginMode(LoginModeEnum.THIRD.getEnumValue());
                this.createUser(user,password);
            }
        }

    }

    private void createUser(User u, String password){
        int defaultOrgId = SettingUtil.getInteger("org", "defaultOrgId", 0);
        String salt = StringTool.uuid();
        u.setSalt(salt);
        String hex = Md5Util.hex(password, salt);
        u.setPassword(hex);
        u.setLocked(0);
        if(defaultOrgId > 0 && (orgService.findOrgObject(defaultOrgId)) != null){
            userService.addUserWidthOrg(u,defaultOrgId);
        }else{
            userService.addUser(u);
        }
    }

    /**
     * 判断用户是否被锁定
     */
    private User validateUserStatus(String userName){
        PasswordPolicyConfig instance = PasswordPolicyConfig.getInstance();
        if(instance.isEnable()){
            //redisUtil.redisTemplate.boundValueOps(CacheKeyEnum.PASSWORD_ERROR_COUNT + "||" + userName).get(0,-1);
            String key = CacheKeyEnum.PASSWORD_ERROR_COUNT + "||" + userName;
            int errorCount = instance.getPasswordMaxErrorCount();
            Long n = redisUtil.incrNow(key);
            if(n != null && n.intValue() > errorCount){
                throw new UserLockException();
            }
        }
        User user = userService.getByUserName(userName);
        if(user != null){
            if(Objects.equals(user.getLocked(),1)){
                throw new UserLockException("用户被锁定");
            }
        }
        return user;
    }

    private void validateCaptcha(LoginRequest request){
        if(PasswordPolicyConfig.getInstance().isCaptchaEnable()){
            String verifyKey = CacheKeyEnum.CAPTCHA_CODE_KEY + StringTool.safeToString(request.getUuid(), "");
            String captcha = redisUtil.get(verifyKey);
            redisUtil.del(verifyKey);

            if (StringTool.isBlank(captcha) || !captcha.equalsIgnoreCase(request.getCode())) {
                throw new CaptchaException();
            }
        }
    }
}
